{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 案例要求\n",
    "\n",
    "\n",
    "把 “人员名片信息.xlsx”中所有的人员，按照“名片模板.docx”文件格式生成的名片"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<table width=\"100%\" border=\"0\">\n",
    "<tr>\n",
    "  <td valign=\"top\" style=\"vertical-align:top; text-align:left;background-color:#ddffdd;\">\n",
    " \n",
    " **<font color=\"blue\" size=\"3px\"> 名片模板.docx（Word）</font>**\n",
    " \n",
    "<img src=\"images/名片模板.png\" align=\"center\" style=\"width:40%\"/>\n",
    "  </td>\n",
    "  </tr>\n",
    "    <tr>\n",
    "  <td style=\"vertical-align:top; text-align:left;background-color:#ddffdd\">\n",
    "\n",
    "**<font color=\"blue\" size=\"3px\"> 人员名片信息.xlsx（Excel）</font>**\n",
    " \n",
    "<img src=\"images/人员名片信息.png\" align=\"center\"/>\n",
    "\n",
    "  </td>\n",
    " </tr>\n",
    "</table>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 导入工具包\n",
    "import docx\n",
    "from docx import Document\n",
    "import copy # 复制模板用\n",
    "import xlrd"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 为了防止综合案例的课程产生的文件过多，我们要对文件进行分类管理\n",
    "# 本课程相关的文件都放到 files_dir 指定的目录下\n",
    "files_dir = \"02_files\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 建立几个函数\n",
    "\n",
    "本次课程的代码逻辑比前期课程的要多，我们建立几个函数的目的就是为了方便代码的阅读。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 新增函数(1) get_namecard_data：从Excel中读取人员名片信息"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_namecard_data(xls):\n",
    "    \"\"\"\n",
    "    定义函数：从Excel文件中读取名片数据\n",
    "    :param xls: Excel 文件\n",
    "    :return: 返回了 2 组数据\n",
    "             1）名片字段列表：['姓名', '职位', '部门', '电话', '手机', '邮箱']\n",
    "             2）名片信息列表（列表中的列表）\n",
    "             [['张三', '开发工程师', '技术部'..],['李四', '业务经理', ...]]\n",
    "    \"\"\"\n",
    "    # 打开文件\n",
    "    workbook = xlrd.open_workbook(xls)\n",
    "    # 获取第一个工作表，从0开始\n",
    "    sheet = workbook.sheet_by_index(0)\n",
    "    # 名片信息列表（列表中的列表）\n",
    "    namecard_infos_list = []\n",
    "    # 从 0 开始\n",
    "    field_list = sheet.row_values(0)\n",
    "    for row_index in range(1, sheet.nrows):\n",
    "        namecard_infos_list.append(sheet.row_values(row_index))\n",
    "    # 返回了 2 组数据\n",
    "    #   1）名片字段列表：['姓名', '职位', '部门', '电话', '手机', '邮箱']\n",
    "    #   2）名片信息列表（列表中的列表）\n",
    "    #      [['张三', '开发工程师', '技术部'..],['李四', '业务经理', ...]]\n",
    "    return field_list, namecard_infos_list"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 测试.1 get_namecard_data\n",
    "\n",
    "用一个变量 namecards 来接收 get_namecard_data 返回的2组数据，看看结果"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "staff_namecard_info_file = f\"./{files_dir}/人员名片信息.xls\"\n",
    "\n",
    "# 获取的人员名片信息虽然有2组返回值，我们还是可以只赋值给一个变量\n",
    "namecards = get_namecard_data(staff_namecard_info_file)\n",
    "# 打印的格式为：( [ ....],[....] ) 最外面是 ** 圆括号 **\n",
    "print(namecards) "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 方括号是列表，圆括号是元组"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 列表 list , 元组 tuple\n",
    "my_list = list()   # 简写语法：my_list = []\n",
    "my_tuple = tuple() # 简写语法：my_tuple = ()\n",
    "print(\"列表:\",my_list)  # 前期课程已经讲过\n",
    "print(\"元组:\",my_tuple) # 本次课程不做要求，知道元组就可以了，平常编程用 list 基本上可以满足 "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 测试.2 get_namecard_data\n",
    "\n",
    "用两个变量 namecard_field_list, namecard_infos_list 来接收 get_namecard_data 返回的2组数据，看看结果"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 获取的人员名片信息有2组返回值，我们**建议赋值给2个变量**\n",
    "namecard_field_list, namecard_infos_list = get_namecard_data(staff_namecard_info_file)\n",
    "print(\"名片字段：\",namecard_field_list)\n",
    "print(\"人员名片信息：\",namecard_infos_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 查看名片模版文件结构"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from docx_ext import read_docx\n",
    "\n",
    "docx_template_file = f\"./{files_dir}/Word_名片模版.docx\"\n",
    "# 查看一下“Word_名片模版.docx”模版中的文字块数据\n",
    "read_docx(docx_template_file)\n",
    "# 可以看到文字块包含 [姓名] [职位] [部门] [电话] [手机] [邮箱]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font color=red>本次课程的目的就是将这些文字块中的 [姓名] [职位] [部门] [电话] [手机] [邮箱] 替换为下面 Excel 的数据</font>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"images/人员名片信息.png\" align=\"left\"/>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 知识点：dict 字典"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 定义一个空字典的语法\n",
    "trans = dict() #  简写语法：trans = {}\n",
    "print(trans)\n",
    "\n",
    "# 给字典加入元素，比如：中英文翻译\n",
    "trans[\"hello\"] = \"你好\"\n",
    "trans[\"bye\"] = \"再见\"\n",
    "\n",
    "print(\"字典数据：\",trans)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 循环遍历 trans.keys()\n",
    "for en in trans.keys():\n",
    "    cn = trans[en] # 根据 en ,获取中文\n",
    "    print(\"英文: %s => 中文: %s\" % (en, cn))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 新增函数(2) gen_replace_dict：将替换列表改为替换字典"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def gen_replace_dict(namecard_infos):\n",
    "    \"\"\"\n",
    "    把列表替换为字典：\n",
    "    ['刘德泽', '开发工程师'....] => {'[姓名]':'刘德泽', '[职位]':'开发工程师'....}\n",
    "    :param namecard_infos: 名片信息列表，格式为：['刘德泽', '开发工程师'....] \n",
    "    :return:  字典格式为：{'[姓名]':'刘德泽', '[职位]':'开发工程师'....}\n",
    "    \"\"\"\n",
    "    replace_dict = {} # 创建一个空字典\n",
    "    for idx, namecard_info in enumerate(namecard_infos):\n",
    "        # namecard_field_list = ['姓名', '职位', .....]\n",
    "        field = namecard_field_list[idx]    # field = 姓名\n",
    "        param = \"[%s]\" % field              # param = [姓名]\n",
    "        replace_dict[param] = namecard_info # [姓名] = 刘德泽\n",
    "    return replace_dict"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 测试 gen_replace_dict"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "namecards = ['刘德泽', '开发工程师', '技术部', '87646000', '13399999999', 'ldz@abcde.com.cn']\n",
    "# 测试 gen_replace_dict\n",
    "replace_dict = gen_replace_dict(namecards)\n",
    "print(\"字段信息：\",namecard_field_list)\n",
    "print(\"名片信息：\",namecards)\n",
    "print(\"字典信息：\",replace_dict)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 知识点：replace 函数 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 定义一个模板\n",
    "message_tpl = \"[姓名]您好！欢迎学习[课程]\"\n",
    "\n",
    "# 进行第一次替换\n",
    "message = message_tpl.replace(\"[姓名]\", \"刘德泽\")\n",
    "print(\"1)\",message)\n",
    "\n",
    "# 进行第二次替换\n",
    "message = message.replace(\"[课程]\", \"Python\")\n",
    "print(\"2)\",message)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 新增函数(3) replace_run：替换文字块内容"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def replace_run(run, replace_dict):\n",
    "    \"\"\"\n",
    "    用于替换 run 对象中的值，比如：[姓名] => 刘德泽\n",
    "    :param run: 需要进行替换的文字块\n",
    "    :param replace_dict: 替换字典，格式为：{'[姓名]':'刘德泽', '[职位]':'开发工程师'....}\n",
    "    :return:\n",
    "    \"\"\"\n",
    "    for key in replace_dict.keys():\n",
    "        # print(key, replace_dict[key])\n",
    "        # 比如：key = [姓名] \n",
    "        # 如果 文字块 run 中包含有 [姓名]，则进行替换 \n",
    "        if key in run.text:  \n",
    "            run.text = run.text.replace(key, replace_dict[key])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 测试 replace_run"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "doc = Document()\n",
    "print(\"替换字典 =>\",replace_dict)\n",
    "p1 = doc.add_paragraph('姓名：[姓名]')\n",
    "p1.add_run(\"  职位：[职位]\")\n",
    "\n",
    "print(\"段落替换前 =>\",p1.text)\n",
    "# 测试 replace_run\n",
    "replace_run(p1.runs[0],replace_dict)\n",
    "print(\"段落替换后 =>\",p1.text)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 新增函数(4) replace_doc：遍历替换文档的段落"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def replace_doc(document, replace_dict):\n",
    "    \"\"\"\n",
    "    替换文档中的 [姓名] [职位] 为 指定的名片信息\n",
    "    :param document: 包含 [姓名] [职位]...的文档模板对象\n",
    "    :param replace_dict: \n",
    "    :return:\n",
    "    \"\"\"\n",
    "    # 循环遍历段落\n",
    "    for paragraph in document.paragraphs:\n",
    "        for run in paragraph.runs:\n",
    "            replace_run(run, replace_dict)\n",
    "\n",
    "    return document"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 新增函数(5) gen_namecard_main：名片批量生成主函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def gen_namecard_main(docx_tpl):\n",
    "    \"\"\"\n",
    "    名片批量生成的主函数\n",
    "    docx_tpl：名片模版文件路径\n",
    "    \"\"\"\n",
    "    # 获取文档对象（作为名片模版）\n",
    "    name_card_tpl = docx.Document(docx_tpl)\n",
    "\n",
    "    # 循环遍历名片信息列表（多个人员的名片数据）\n",
    "    for namecard_info in namecard_infos_list:\n",
    "        # 每次都要复制一份模板，防止原始的模板被替换了\n",
    "        name_card_doc = copy.deepcopy(name_card_tpl)\n",
    "        # 开始替换文档内容（参数分别为：docx模板副本 和 当前员工的名片数据）\n",
    "        # namecard_info=['刘德泽', '开发工程师', '技术部', '87646000', '13399999999', 'ldz@abcde.com.cn']\n",
    "    #     replace_doc(name_card_doc, namecard_info)\n",
    "        replace_dict = gen_replace_dict(namecard_info)\n",
    "        replace_doc(name_card_doc,replace_dict )\n",
    "        # 获取人员姓名\n",
    "        staff_name = namecard_info[0]\n",
    "        name_card_doc.save(f'./{files_dir}/{staff_name}的名片.docx')\n",
    "\n",
    "# 直接调用主函数，参数为：名片模版文件 \n",
    "gen_namecard_main(docx_template_file)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Python总结"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1、元组的了解  \n",
    "```\n",
    "my_tuple = tuple()\n",
    "\n",
    "my_tuple = ()\n",
    "```\n",
    "2、字典的用法\n",
    "```\n",
    "my_dict = dict()\n",
    "\n",
    "my_dict = {}\n",
    "```\n",
    "\n",
    "<font color=red>大家注意括号不要记混了： 列表 [ ]、元组 ( )、字典 { } </font>\n",
    "\n",
    "3、创建函数可以提高代码的阅读性\n",
    "\n",
    "* 可以看到每个函数实现的功能比较单一，代码量都比较少，这样代码易读性就好的多；\n",
    "\n",
    "* 另外，创建的函数还可以重复利用多次；\n",
    "```\n",
    "比如：下面思考题中会重复再用一次 gen_namecard_main() 生成名片的主函数，只是传入的参数（模板文件）不同\n",
    "```\n",
    "* 如果把所有的逻辑写在一起，代码肯定很长了，大家可以尝试不用函数，把所有的逻辑写在一起；\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 思考题"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "运行如下代码，“Word_名片模版_思考题.doc”模板中有一个段落中的内容 [职位] 有3个文字块，刚才的代码就无法正确生成名片了，怎么办？\n",
    "\n",
    "这个思考题有点难度，能解决的朋友，就非常厉害了，不能解决也没有关系，只要思考了，思维能力也一定能得到锻炼的，加油！"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 运行查看“Word_名片模版_思考题.docx”，可以看到 “[职位]” 属于3个文字块，无法成功替换\n",
    "thinking_tpl_file = f\"./{files_dir}/Word_名片模版_思考题.docx\"\n",
    "# 查看一下“Word_名片模版_思考题.docx”模版中的文字块数据\n",
    "read_docx(thinking_tpl_file)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 用“Word_名片模版_思考题.docx” 生成名片看看，会出现什么问题？\n",
    "gen_namecard_main(thinking_tpl_file)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
